﻿// <copyright>
// Copyright (c) 2008 - 2009, Microsoft, All Rights Reserved
// </copyright>
//
// Licensed under the Microsoft Public License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//
// http://www.opensource.org/licenses/ms-pl.html

namespace LiveLabs.Logging
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;

    /// <summary>
    /// Class that makes working with names of Loggers easier.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This class maintains all the rules for the names of Loggers, such
    /// as case insensitive equality and comparison, and normalization of
    /// names derived from <see cref="System.Type"/>.
    /// </para>
    /// <para>
    /// In addition, a LoggerName knows how to enumerate the hierarchy it
    /// belongs to.  For example, if you have a LoggerName a.b.c.d.e, the
    /// instance would be able to return an IEnumerable
    /// consisting of LoggerNames a, a.b, a.b.c, a.b.c.d, and a.b.c.d.e.
    /// <see cref="Hierarchy"/> for more details.
    /// </para>
    /// </remarks>
    public sealed class LoggerName
        : IEquatable<LoggerName>,
          IComparable<LoggerName>
    {
        private readonly string fullName;
        private readonly string baseName;

        /// <summary>
        /// Initializes a new instance of the LoggerName class. 
        /// </summary>
        /// <param name="name">The name to use</param>
        /// <exception cref="ArgumentNullException">If name is null or empty</exception>
        public LoggerName(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            this.fullName = name;
            this.baseName = ExtractRoot(this.fullName);
        }

        /// <summary>
        /// Initializes a new instance of the LoggerName class.
        /// </summary>
        /// <param name="type">The type to use</param>
        /// <remarks>
        /// <para>
        /// If the <paramref name="type"/> <see cref="System.Type.IsNested"/>,
        /// the default delimiter is a '+' character.  We normalize this into a dot.
        /// </para>
        /// <para>
        /// If the <paramref name="type"/> <see cref="System.Type.IsGenericType"/>,
        /// the default string representation is to `N where N is the number of
        /// generic types.  A list of the types is contained in an array ([]) on the
        /// inner most type.  The `N is turned into _N.
        /// </para>
        /// <para>
        /// <code>
        /// public class Foo&lt;T1,T2>
        /// {
        ///     public class Inner&lt;T3>
        ///     {
        ///     }
        /// }
        /// string name = typeof(Foo&lt;string,int>.Inner&lt;DateTime>).FullName;
        /// </code>
        /// name is Foo`2+Inner`1[[System.String, assembly info],[System.Int32, assembly info],[System.DateTime, assembly info]]
        /// after normalization name is foo_2.inner_1
        /// </para>
        /// <para>
        /// If you access pass in the true generic type (i.e. Foo&lt;,>.Inner&lt;>), the bracketed types are not present.  The
        /// normalization result is the same though.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">If type is null</exception>
        public LoggerName(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            string name = type.FullName;

#if SILVERLIGHT
            name = name.Replace('+', '.');
#else
            if (type.IsNested)
            {
                name = name.Replace('+', '.');
            }
#endif

            if (type.IsGenericType)
            {
                // generic types as strings, use `N where N is the number
                // of generic types.  After the N is the list of all the types 
                // complete with assembly information.  We just want the `N
                // part.
                int delim = name.IndexOf('`');
                Debug.Assert(delim != -1, "Generic type array delimiter has changed from '`'");

                // if it is a real implementation (i.e. Foo<string,string> vs. Foo<,>) the types
                // are listed at the end of the name surround with []
                int typeArrayBegin = name.IndexOf("[[", delim, StringComparison.OrdinalIgnoreCase);
                if (typeArrayBegin != -1)
                {
                    name = name.Substring(0, typeArrayBegin);
                }

                name = name.Replace('`', '_');
            }

            this.fullName = name;
            this.baseName = ExtractRoot(this.fullName);
        }

        /// <summary>
        /// Gets the FullName of the LoggerName
        /// </summary>
        public string FullName
        {
            get { return this.fullName; }
        }

        /// <summary>
        /// Gets the BaseName of the LoggerName.
        /// </summary>
        public string BaseName
        {
            get { return this.baseName; }
        }

        /// <summary>
        /// Gets the hierarchy of LoggerNames this instance belongs to
        /// </summary>
        public IEnumerable<LoggerName> Hierarchy
        {
            get
            {
                // we always return ourself first...
                yield return this;

                // move through the name to return the hierarchy
                string name = this.fullName;
                int lastdot = name.LastIndexOf('.');
                while (lastdot != -1)
                {
                    name = name.Substring(0, lastdot);
                    lastdot = name.LastIndexOf('.');

                    yield return new LoggerName(name);
                }
            }
        }

        #region Overloaded operators

        /// <summary>
        /// The equal operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left is equal to right, otherwise false</returns>
        public static bool operator ==(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) == 0;
        }

        /// <summary>
        /// The not equal operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left is not equal to right, otherwise false</returns>
        public static bool operator !=(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) != 0;
        }

        /// <summary>
        /// The less than operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left is less than right, false otherwise</returns>
        public static bool operator <(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) < 0;
        }

        /// <summary>
        /// The less than or equal to operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left is less than or equal to right, false otherwise</returns>
        public static bool operator <=(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) <= 0;
        }

        /// <summary>
        /// The greater than operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left is greater than right, false otherwise</returns>
        public static bool operator >(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) > 0;
        }

        /// <summary>
        /// The greater than or equal to operator
        /// </summary>
        /// <param name="left">The left hand side</param>
        /// <param name="right">The right hand side</param>
        /// <returns>True if left greater than or equal to right, false otherwise</returns>
        public static bool operator >=(LoggerName left, LoggerName right)
        {
            return left.CompareTo(right) >= 0;
        }

        #endregion

        /// <summary>
        /// Checks this instance for equality to passed in obj
        /// </summary>
        /// <param name="obj">The object to check for equality</param>
        /// <returns>True if equal, false otherwise</returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as LoggerName);
        }

        /// <summary>
        /// Get a hashcode from this instance
        /// </summary>
        /// <returns>An integer value to use as our hash code</returns>
        public override int GetHashCode()
        {
            return this.fullName.GetHashCode();
        }

        /// <summary>
        /// Get a string representation of this instance
        /// </summary>
        /// <returns>A string representation of this instance</returns>
        public override string ToString()
        {
            return this.fullName;
        }

        #region IEquatable<LoggerName> Members

        /// <summary>
        /// Checks this instance for equality to passed in obj
        /// </summary>
        /// <param name="other">The object to check for equality</param>
        /// <returns>True if equal, false otherwise</returns>
        public bool Equals(LoggerName other)
        {
            if (object.ReferenceEquals(null, other))
            {
                return false;
            }

            if (object.ReferenceEquals(this, other))
            {
                return true;
            }

            return this.fullName.Equals(other.fullName, StringComparison.OrdinalIgnoreCase);
        }

        #endregion

        #region IComparable<LoggerName> Members

        /// <summary>
        /// Compares this instance to another.
        /// </summary>
        /// <param name="other">The object to compare to</param>
        /// <returns>-1 if less than, 0 if equal, and 1 if greater than</returns>
        public int CompareTo(LoggerName other)
        {
            if (object.ReferenceEquals(null, other))
            {
                return 1;
            }

            if (object.ReferenceEquals(this, other))
            {
                return 0;
            }

            return string.Compare(this.fullName, other.fullName, StringComparison.OrdinalIgnoreCase);
        }

        #endregion

        private static string ExtractRoot(string fullName)
        {
            string result = fullName;

            int lastdot = fullName.LastIndexOf('.');
            if (lastdot != -1)
            {
                result = fullName.Substring(lastdot + 1);
            }

            return result;
        }
    }
}
